package com.devmindset.jpatoolbox.dao;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;


import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.devmindset.jpatoolbox.base.BaseEntity;

/**
 * Generic DAO object.
 * @param <T> model type
 */
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
@Repository
public class GenericDAO<T extends BaseEntity> {

  @PersistenceContext
  private EntityManager em;

  /**
   * Merges a object into the persistent context and returns a managed object.
   * If the object passed as input parameter
   * is persistent then an update is invoked, otherwise an insert is invoked.
   * @param <K> type of the data object
   * @param object object to save / merge
   * @return managed, persistent object
   */
  public <K extends T> K save(K object) {
    if (object.getId() == null) {
      em.persist(object);
      return object;
    } else {
      return em.merge(object);
    }
  }
    
  /**
   * Flushed the changes into the database.
   * <p> Should be used in the tests only.
   */
  @Transactional(propagation = Propagation.REQUIRED)
  public void flush() {
    em.flush();
  }
  
  /**
   * Creates and returns a query.
   * @param queryString queryString
   * @return query
   */
  protected Query createQuery(String queryString) {
    return em.createQuery(queryString);
  }
  
  /**
   * Gets an object from the data storage.
   * This method should be used by the application logic.
   * @param <K> type of the retrieved object
   * @param clazz class of the retrieved object
   * @param objectId unique object id
   * @return persistent object
   */
  @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
  public <K extends T> K find(Class<K> clazz,  final Long objectId) {
    if (objectId == null) {
      throw new IllegalArgumentException("input must not be empty");
    }
    final K object = em.find(clazz, objectId);
    return object;
  }

  /**
   * Inserts an object into a database. If does not matter if the object passed as input parameter
   * is persistent or transient - a new entry is still created.
   * Use the method in tests, in the application use the save method.
   * @param object object to insert
   */
  @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
  protected void insert(T object) {
    em.persist(object);
  }

  /**
   * Inserts an list of objects into a database. If does not matter if the objects passed as input parameters
   * are persistent or transient - new entries are still created.
   * @param objects objects to insert
   */
  @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
  protected void insert(List<T> objects) {
    for (T object : objects) {
      insert(object);
    }
  }

  /**
   * Deletes an object from a database.
   * @param object object to delete
   */
  @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
  protected void delete(T object) {
    em.remove(object);
  }

  /**
   * Deletes a list of objects from a database.
   * @param objects objects to delete
   */
  @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
  protected void delete(List<T> objects) {
    for (T object : objects) {
      delete(object);
    }
  }

}
